// physical-html
//
// Copyright (c)  Daigo Tanaka
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

physHtml.iForceSource = function()
{
  this.position = Vector.Zero; // origin of spring
  this.orientation = 0.0;
  this.width = 0.0;
  this.height = 50;
  this.offset = $V([0.0, -0.5]);
  this.rotationOffset = $V([0.5, 0.5]);
  this.radius = 0;
  this.naturalLength = 100.0;
  this.currentLength = 100.0;
  this.spring = 0.0;
  this.damping = 0.0;
  
  this.force = function(position, radius, velocity)
  {
    throw "Error: Cannot invoke a method of an interface";
  }

  this.velocity = function(position, radius, velocity)
  {
    throw "Error: Cannot invoke a method of an interface";
  }

  this.stretchLimit = function(mass, delta)
  {
    throw "Error: Cannot invoke a method of an interface";
  }
}

physHtml.aForceSource = function()
{
  physHtml.iForceSource.call(this);

  this.force = function(position, radius, velocity)
  {
    return Vector.Zero(2);
  }

  this.velocity = function(position, radius, velocity)
  {
    return velocity;
  }
}

physHtml.springDamper = function()
{
  physHtml.aForceSource.call(this);
  this.force = function(position, radius, velocity)
  {
    this.currentLength = position.distanceFrom(this.position);
    this.radius = this.currentLength * 0.5;
    this.width = this.radius * 2;
    var stretch = this.currentLength - this.naturalLength;
//    if (stretch < 0) stretch = 0; // rubber band effect
    var stretchDir = position.subtract(this.position).toUnitVector();
    return stretchDir.x( -1 * stretch * this.spring ).subtract( velocity.x( this.damping ) );
  }

  this.stretchLimit = function(mass, delta)
  {
    return this.naturalLength * mass / (delta * delta * this.spring);
  }
}

physHtml.slider = function(slideDirection)
{
  physHtml.aForceSource.call(this);
  this.position = $V([0.0, 0.0]);
  this.width = 0.5;
  this.height = 0.5;
  this.orientation = 0.0;
  this.offset = $V([-0.5, -0.5]);
  this.rotationOffset = $V([0.0, 0.0]);
  this.radius = 0.0;
  this.slideDirection = slideDirection.toUnitVector();

  this.velocity = function(position, radius, velocity)
  {
    var velocityDir = velocity.toUnitVector();
    var scale = velocityDir.dot(this.slideDirection);
    return this.slideDirection.x(velocity.distanceFrom(Vector.Zero(2)) * scale);
  }
}

physHtml.wall = function(position, orientation, width, height, bounce)
{
  physHtml.aForceSource.call(this);
  this.position = position;
  this.orientation = orientation / 180.0 * Math.PI;
  this.width = width;
  this.height = height;
  this.offset = $V([0, 0]);
  this.rotationOffset = $V([0.0, 0.0]);
  this.bounce = bounce;
  this.radius = 0.0;
  this.wallNormalDirection = $V([Math.sin(this.orientation), -Math.cos(this.orientation)]).toUnitVector();
  this.originalOrientation = this.orientation;

  this.velocity = function(position, radius, velocity)
  {
    this.orientation = this.originalOrientation;
    var projectedPosition = $V([position.e(physHtml.Index0) * Math.abs(this.wallNormalDirection.e(physHtml.Index0)), position.e(physHtml.Index1) * Math.abs(this.wallNormalDirection.e(physHtml.Index1))]);
    var projectedVelocity = $V([velocity.e(physHtml.Index0) * Math.abs(this.wallNormalDirection.e(physHtml.Index0)), velocity.e(physHtml.Index1) * Math.abs(this.wallNormalDirection.e(physHtml.Index1))]);
    var bounce = this.bounce;
//    if (projectedVelocity.distanceFrom(Vector.Zero(2)) < VelocityCutOff * 100 * this.bounce) bounce = 0;
    if (Math.abs( this.position.e(physHtml.Index0) - projectedPosition.e(physHtml.Index0) + this.position.e(physHtml.Index1) - projectedPosition.e(physHtml.Index1) ) < radius
         && velocity.dot(this.wallNormalDirection) < 0.0 ){
      return $V([ velocity.e(physHtml.Index0) * (-1 * Math.abs( this.wallNormalDirection.e(physHtml.Index0) ) + 1) + velocity.e(physHtml.Index0) * Math.abs( this.wallNormalDirection.e(physHtml.Index0) ) * -1 * bounce,   
                  velocity.e(physHtml.Index0) * (-1 * Math.abs( this.wallNormalDirection.e(physHtml.Index1) ) + 1) + velocity.e(physHtml.Index1) * Math.abs( this.wallNormalDirection.e(physHtml.Index1) ) * -1 * bounce
                ]);
    }
    return velocity;
  }
}


